//
// "$Id: Fl_Widget.cxx 9452 2012-05-07 07:18:11Z AlbrechtS $"
//
// Base widget class for the Fast Light Tool Kit (FLTK).
//
// Copyright 1998-2010 by Bill Spitzak and others.
//
// This library is free software. Distribution and use rights are outlined in
// the file "COPYING" which should have been included with this file.  If this
// file is missing or damaged, see the license at:
//
//     http://www.fltk.org/COPYING.php
//
// Please report all bugs and problems on the following page:
//
//     http://www.fltk.org/str.php
//

#include "Fl.H"
#include "Fl_Widget.H"
#include "Fl_Group.H"
#include "Fl_Tooltip.H"
#include "fl_draw.H"
#include <stdlib.h>
#include "flstring.h"


////////////////////////////////////////////////////////////////
// for compatibility with Forms, all widgets without callbacks are
// inserted into a "queue" when they are activated, and the forms
// compatibility interaction functions (fl_do_events, etc.) will
// read one widget at a time from this queue and return it:

const int QUEUE_SIZE = 20;

static Fl_Widget *obj_queue[QUEUE_SIZE];
static int obj_head, obj_tail;

void Fl_Widget::default_callback(Fl_Widget *o, void * /*v*/)
{
#if 0
	// This is necessary for strict forms compatibility but is confusing.
	// Use the parent's callback if this widget does not have one.
	for (Fl_Widget *p = o->parent(); p; p = p->parent())
		if (p->callback() != default_callback) {
			p->do_callback(o,v);
			return;
		}
#endif
	obj_queue[obj_head++] = o;
	if (obj_head >= QUEUE_SIZE) obj_head = 0;
	if (obj_head == obj_tail) {
		obj_tail++;
		if (obj_tail >= QUEUE_SIZE) obj_tail = 0;
	}
}
/**
    Reads the default callback queue and returns the first widget.

    All Fl_Widgets that don't have a callback defined use the default
    callback \p static Fl_Widget::default_callback() that puts a pointer
    to the widget in a queue. This method reads the oldest widget out
    of this queue.

    The queue (FIFO) is limited (currently 20 items). If the queue
    overflows, the oldest entry (Fl_Widget *) is discarded.

    Relying on the default callback and reading the callback queue with
    Fl::readqueue() is not recommended. If you need a callback, you should
    set one with Fl_Widget::callback(Fl_Callback *cb, void *data)
    or one of its variants.

    \see Fl_Widget::callback()
    \see Fl_Widget::callback(Fl_Callback *cb, void *data)
    \see Fl_Widget::default_callback()
*/
Fl_Widget *Fl::readqueue()
{
	if (obj_tail==obj_head) return 0;
	Fl_Widget *o = obj_queue[obj_tail++];
	if (obj_tail >= QUEUE_SIZE) obj_tail = 0;
	return o;
}
/*
    This static internal function removes all pending callbacks for a
    specific widget from the default callback queue (Fl::readqueue()).
    It is only called from Fl_Widget's destructor if the widget
    doesn't have an own callback.
    Note: There's no need to have this in the Fl:: namespace.
*/
static void cleanup_readqueue(Fl_Widget *w)
{

	if (obj_tail==obj_head) return;

	// Read the entire queue and copy over all valid entries.
	// The new head will be determined after the last copied entry.

	int old_head = obj_head;	// save newest entry
	int entry = obj_tail;		// oldest entry
	obj_head = obj_tail;		// new queue start
	for (;;) {
		Fl_Widget *o = obj_queue[entry++];
		if (entry >= QUEUE_SIZE) entry = 0;
		if (o != w) { // valid entry
			obj_queue[obj_head++] = o;
			if (obj_head >= QUEUE_SIZE) obj_head = 0;
		} // valid entry
		if (entry == old_head) break;
	}
	return;
}
////////////////////////////////////////////////////////////////

int Fl_Widget::handle(int)
{
	return 0;
}

/** Default font size for widgets */
Fl_Fontsize FL_NORMAL_SIZE = 14;

Fl_Widget::Fl_Widget(int X, int Y, int W, int H, const char* L)
{

	x_ = X;
	y_ = Y;
	w_ = W;
	h_ = H;

	label_.value	 = L;
	label_.image   = 0;
	label_.deimage = 0;
	label_.type	 = FL_NORMAL_LABEL;
	label_.font	 = FL_HELVETICA;
	label_.size	 = FL_NORMAL_SIZE;
	label_.color	 = FL_FOREGROUND_COLOR;
	label_.align_	 = FL_ALIGN_CENTER;
	tooltip_       = 0;
	callback_	 = default_callback;
	user_data_ 	 = 0;
	type_		 = 0;
	flags_	 = VISIBLE_FOCUS;
	damage_	 = 0;
	box_		 = FL_NO_BOX;
	color_	 = FL_GRAY;
	color2_	 = FL_GRAY;
	when_		 = FL_WHEN_RELEASE;

	parent_ = 0;
	if (Fl_Group::current()) Fl_Group::current()->add(this);
	if (!fl_graphics_driver) {
		// Make sure fl_graphics_driver is initialized. Important if we are called by a static initializer.
		Fl_Display_Device::display_device();
	}
}

void Fl_Widget::resize(int X, int Y, int W, int H)
{
	x_ = X;
	y_ = Y;
	w_ = W;
	h_ = H;
}

// this is useful for parent widgets to call to resize children:
int Fl_Widget::damage_resize(int X, int Y, int W, int H)
{
	if (x() == X && y() == Y && w() == W && h() == H) return 0;
	resize(X, Y, W, H);
	redraw();
	return 1;
}

int Fl_Widget::take_focus()
{
	if (!takesevents()) return 0;
	if (!visible_focus()) return 0;
	if (!handle(FL_FOCUS)) return 0; // see if it wants it
	if (contains(Fl::focus())) return 1; // it called Fl::focus for us
	Fl::focus(this);
	return 1;
}

extern void fl_throw_focus(Fl_Widget*); // in Fl_x.cxx

/**
   Destroys the widget, taking care of throwing focus before if any.
   Destruction removes the widget from any parent group! And groups when
   destroyed destroy all their children. This is convenient and fast.
*/
Fl_Widget::~Fl_Widget()
{
	Fl::clear_widget_pointer(this);
	if (flags() & COPIED_LABEL) free((void *)(label_.value));
	if (flags() & COPIED_TOOLTIP) free((void *)(tooltip_));
	// remove from parent group
	if (parent_) parent_->remove(this);
#ifdef DEBUG_DELETE
	if (parent_) { // this should never happen
		printf("*** Fl_Widget: parent_->remove(this) failed [%p,%p]\n",parent_,this);
	}
#endif // DEBUG_DELETE
	parent_ = 0; // Don't throw focus to a parent widget.
	fl_throw_focus(this);
	// remove stale entries from default callback queue (Fl::readqueue())
	if (callback_ == default_callback) cleanup_readqueue(this);
}

/** Draws a focus box for the widget at the given position and size */
void Fl_Widget::draw_focus(Fl_Boxtype B, int X, int Y, int W, int H) const
{
	if (!Fl::visible_focus()) return;
	switch (B) {
	case FL_DOWN_BOX:
	case FL_DOWN_FRAME:
	case FL_THIN_DOWN_BOX:
	case FL_THIN_DOWN_FRAME:
		X ++;
		Y ++;
	default:
		break;
	}

	fl_color(fl_contrast(FL_BLACK, color()));

#if __FLTK_WIN32__
	// Windows 95/98/ME do not implement the dotted line style, so draw
	// every other pixel around the focus area...
	//
	// Also, QuickDraw (MacOS) does not support line styles specifically,
	// and the hack we use in fl_line_style() will not draw horizontal lines
	// on odd-numbered rows...
	int i, xx, yy;

	X += Fl::box_dx(B);
	Y += Fl::box_dy(B);
	W -= Fl::box_dw(B) + 2;
	H -= Fl::box_dh(B) + 2;

	for (xx = 0, i = 1; xx < W; xx ++, i ++) if (i & 1) fl_point(X + xx, Y);
	for (yy = 0; yy < H; yy ++, i ++) if (i & 1) fl_point(X + W, Y + yy);
	for (xx = W; xx > 0; xx --, i ++) if (i & 1) fl_point(X + xx, Y + H);
	for (yy = H; yy > 0; yy --, i ++) if (i & 1) fl_point(X, Y + yy);
#elif __FLTK_WINCE__
	// Windows 95/98/ME do not implement the dotted line style, so draw
	// every other pixel around the focus area...
	//
	// Also, QuickDraw (MacOS) does not support line styles specifically,
	// and the hack we use in fl_line_style() will not draw horizontal lines
	// on odd-numbered rows...
	int i, xx, yy;

	X += Fl::box_dx(B);
	Y += Fl::box_dy(B);
	W -= Fl::box_dw(B) + 2;
	H -= Fl::box_dh(B) + 2;

	for (xx = 0, i = 1; xx < W; xx ++, i ++) if (i & 1) fl_point(X + xx, Y);
	for (yy = 0; yy < H; yy ++, i ++) if (i & 1) fl_point(X + W, Y + yy);
	for (xx = W; xx > 0; xx --, i ++) if (i & 1) fl_point(X + xx, Y + H);
	for (yy = H; yy > 0; yy --, i ++) if (i & 1) fl_point(X, Y + yy);
#elif __FLTK_MACOSX__
	fl_line_style(FL_DOT);
	fl_rect(X + Fl::box_dx(B), Y + Fl::box_dy(B),
	        W - Fl::box_dw(B) - 1, H - Fl::box_dh(B) - 1);
	fl_line_style(FL_SOLID);
#elif __FLTK_IPHONEOS__
	fl_line_style(FL_DOT);
	fl_rect(X + Fl::box_dx(B), Y + Fl::box_dy(B), W - Fl::box_dw(B) - 1, H - Fl::box_dh(B) - 1);
	fl_line_style(FL_SOLID);
#elif __FLTK_LINUX__
	fl_line_style(FL_DOT);
	fl_rect(X + Fl::box_dx(B), Y + Fl::box_dy(B), W - Fl::box_dw(B) - 1, H - Fl::box_dh(B) - 1);
	fl_line_style(FL_SOLID);
#elif __FLTK_S60v32__
	fl_line_style(FL_DOT);
	fl_rect(X + Fl::box_dx(B), Y + Fl::box_dy(B), W - Fl::box_dw(B) - 1, H - Fl::box_dh(B) - 1);
	fl_line_style(FL_SOLID);
#elif __FLTK_ANDROID__
	fl_line_style(FL_DOT);
	fl_rect(X + Fl::box_dx(B), Y + Fl::box_dy(B), W - Fl::box_dw(B) - 1, H - Fl::box_dh(B) - 1);
	fl_line_style(FL_SOLID);
#else
#error unsupported platform
#endif
}


void Fl_Widget::activate()
{
	if (!active()) {
		clear_flag(INACTIVE);
		if (active_r()) {
			redraw();
			redraw_label();
			handle(FL_ACTIVATE);
			if (inside(Fl::focus())) Fl::focus()->take_focus();
		}
	}
}

void Fl_Widget::deactivate()
{
	if (active_r()) {
		set_flag(INACTIVE);
		redraw();
		redraw_label();
		handle(FL_DEACTIVATE);
		fl_throw_focus(this);
	} else {
		set_flag(INACTIVE);
	}
}

int Fl_Widget::active_r() const
{
	for (const Fl_Widget* o = this; o; o = o->parent())
		if (!o->active()) return 0;
	return 1;
}

void Fl_Widget::show()
{
	if (!visible()) {
		clear_flag(INVISIBLE);
		if (visible_r()) {
			redraw();
			redraw_label();
			handle(FL_SHOW);
			if (inside(Fl::focus())) Fl::focus()->take_focus();
		}
	}
}

void Fl_Widget::hide()
{
	if (visible_r()) {
		set_flag(INVISIBLE);
		for (Fl_Widget *p = parent(); p; p = p->parent())
			if (p->box() || !p->parent()) {
				p->redraw();
				break;
			}
		handle(FL_HIDE);
		fl_throw_focus(this);
	} else {
		set_flag(INVISIBLE);
	}
}

int Fl_Widget::visible_r() const
{
	for (const Fl_Widget* o = this; o; o = o->parent())
		if (!o->visible()) return 0;
	return 1;
}

// return true if widget is inside (or equal to) this:
// Returns false for null widgets.
int Fl_Widget::contains(const Fl_Widget *o) const
{
	for (; o; o = o->parent_) if (o == this) return 1;
	return 0;
}


void
Fl_Widget::label(const char *a)
{
	if (flags() & COPIED_LABEL) {
		// reassigning a copied label remains the same copied label
		if (label_.value == a)
			return;
		free((void *)(label_.value));
		clear_flag(COPIED_LABEL);
	}
	label_.value=a;
	redraw_label();
}


void
Fl_Widget::copy_label(const char *a)
{
	// reassigning a copied label remains the same copied label
	if ((flags() & COPIED_LABEL) && (label_.value == a))
		return;
	if (a) {
		label(strdup(a));
		set_flag(COPIED_LABEL);
	} else {
		label(0);
	}
}

/** Calls the widget callback.

  Causes a widget to invoke its callback function with arbitrary arguments.

  \param[in] o call the callback with \p o as the widget argument
  \param[in] arg use \p arg as the user data argument
  \see callback()
*/
void Fl_Widget::do_callback(Fl_Widget* o,void* arg)
{
	Fl_Widget_Tracker wp(this);
	callback_(o,arg);
	if (wp.deleted()) return;
	if (callback_ != default_callback)
		clear_changed();
}

//
// End of "$Id: Fl_Widget.cxx 9452 2012-05-07 07:18:11Z AlbrechtS $".
//
